package com.wise.transactionsyn.sample2;

import com.wise.domain.DemoEntity;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

/**
 * <title:如何在数据库事务提交成功后进行异步操作>
 * <p>
 * 问题
 * 业务场景
 * 业务需求上经常会有一些边缘操作，比如主流程操作A：用户报名课程操作入库，边缘操作B：发送邮件或短信通知。
 * <p>
 * 业务要求
 * 操作A操作数据库失败后，事务回滚，那么操作B不能执行。
 * <p>
 * 操作A执行成功后，操作B也必须执行成功
 * <p>
 * 如何实现
 * 普通的执行A，之后执行B，是可以满足要求1，对于要求2通常需要设计补偿的操作
 * <p>
 * 一般边缘的操作，通常会设置成为异步的，以提升性能，比如发送MQ，业务系统负责事务成功后消息发送成功，然后接收系统负责保证通知成功完成
 * <p>
 * 本文内容
 * 如何在spring事务提交之后进行异步操作，这些异步操作必须得在该事务成功提交后才执行，回滚则不执行。
 * <p>
 * 要点
 * 如何在spring事务提交之后操作
 * <p>
 * 如何把操作异步化
 */
public class Introduce {
    /**
     * 实现方案一:使用TransactionSynchronizationManager在事务提交之后操作
     * 该方法就可以实现在事务提交之后进行操作
     * 同步
     */
    public void insertSync(DemoEntity entity) {
        //demoMapper.insert(entity);
        //send after tx commit bu is sync(因为是使用的ThreadLocal所以是和当前线程绑定，只要检测到当前线程有事务行为就会执行下面操作)
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                System.out.println("send email after transaction commit...");
            }
        });
        //使用threadLocalRandom防止多线程并发问题
        ThreadLocalRandom random = ThreadLocalRandom.current();
        if (random.nextInt() % 2 == 0) {
            throw new RuntimeException("test email transaction");
        }
        System.out.println("service end");
    }

    /**
     * 方案二：操作异步化
     * 使用mq或线程池来进行异步，比如使用线程池
     */
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

    public void insertAsync(DemoEntity entity) {
        //demoMapper.insert(entity);
        //send after tx commit and is async
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("send email after transaction commit ...");
                        try {
                            Thread.sleep(10 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("complete send email after transaction commit...");
                    }
                });
            }
        });
        //async work but tx not work, execute even when tx is rollback
        //asyncService.executeAfterTxComplete();
        ThreadLocalRandom random = ThreadLocalRandom.current();
        if (random.nextInt() % 2 == 0) {
            throw new RuntimeException("test email transaction async");
        }
        System.out.println("service end");
    }

    /**
     * 封装以上两步
     * 对于第二步来说，如果这类方法比较多的话，则写起来重复性太多，因而，抽象出来如下：
     * 这里改造了azagorneanu的代码:
     * @See
     */
}
